﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Collections;

namespace worldParser
{
    public class CWorldParserClass
    {
        //TBackground BackGround;
        Stack<IWorldStackObject> worldObjStack;
        Stack<string> deferredParams;
        public Dictionary<string, IWorldStackObject> defDictionary;
        string dicKey;

        StreamReader readStream;
        public CWorldArray Root;

        List<string> Feed()
        {     
            {
                string s = "";
                do
                {
                    s = readStream.ReadLine();
                    if (null == s) return null;
                    s = s.Trim();
                } while ((0 == s.Length) || ('#' == s[0])); // pass comments and empty lines

                List<string> ret = new List<string>(s.Split(' '));

                int i=0;
                while (i < ret.Count)
                {
                    if ('"' == ret[i][0])
                    {
                        int j = i;
                        StringBuilder sb = new StringBuilder(ret[i]);

                        if ('"' != ret[i][ret[i].Length - 1])
                            do
                            {
                                sb.Append(" ");
                                sb.Append(ret[i + 1]);                                
                                ret.RemoveAt(i);                                
                            } while ('"' != ret[i][ret[i].Length - 1]);
               
                        ret[i] = sb.ToString().Trim('"');
                    }
                    i++;
                }
                return ret;
            }
        }

        private void WriteError(string s)
        {
            Console.BackgroundColor = ConsoleColor.DarkRed;
            Console.WriteLine(s);
            Console.BackgroundColor = ConsoleColor.Black;
        }

        private void AddToDictionary(IWorldStackObject obj)
        {
            try
            {
                (obj as CWorldObject).Reused = true;
                defDictionary.Add(dicKey, obj);
            }
            catch (ArgumentException)
            {
                defDictionary[dicKey] = obj;
            }
            dicKey = null;
        }

        public void PushToStack(IWorldStackObject obj)
        {
            if (null != dicKey)
            {
                AddToDictionary(obj);
            }
            worldObjStack.Push(obj);
        }

        private void PushFromDictionary(string key)
        {
            if (dicKey != null) throw new Exception("there is selfloops in the stack");

            PushToStack(defDictionary[key]);
        }

        public IWorldStackObject StackPop()
        {
            IWorldStackObject o=worldObjStack.Pop();
            
            if (o == Root)  throw new Exception("Stack is broken");
            return o;
        }

        void IsWorldObject(string s)
        {
            switch (s.ToLower())
            {
                case "background":
                    PushToStack(new CBackground());
                    break;

                case "worldinfo":
                    PushToStack(new CWorldInfo());
                    break;

                case "viewpoint":
                    PushToStack(new CViewpoint());
                    break;

                case "pointlight":
                    PushToStack(new CPointLight());
                    break;

                case "solid":
                    PushToStack(new CSolid());
                    break;

                case "shape":
                    PushToStack(new CShape());
                    break;

                case "customrobot":
                    PushToStack(new CCustomRobot());
                    break;

                case "differentialwheels":
                    PushToStack(new CDifferentialWheels());
                    break; 

                case "supervisor":
                    PushToStack(new CSupervisor());
                    break;   

                case "transform":
                    PushToStack(new CTransform());
                    break;

                case "servo":
                    PushToStack(new CServo());
                    break;

                case "distancesensor":
                    PushToStack(new CDistanceSensor());
                    break;

                case "physics":
                    PushToStack(new CPhysics());
                    break;

                case "cone":
                    PushToStack(new CCone());
                    break;

                case "box":
                    PushToStack(new CBox());
                    break;

                case "sphere":
                    PushToStack(new CSphere());
                    break;

                case "extrusion":
                    PushToStack(new CExtrusion());
                    break;                    

                case "group":
                    PushToStack(new CGroup());
                    break;

                case "indexedfaceset":
                    PushToStack(new CIndexedFaceSet());
                    break;

                case "elevationgrid":
                    PushToStack(new CElevationGrid());
                    break;

                case "color":
                    PushToStack(new CColors());
                    break;

                case "material":
                    PushToStack(new CMaterial());
                    break;

                case "appearance":
                    PushToStack(new CAppearance());
                    break;

                case "camera":
                    PushToStack(new CCamera());
                    break;

                case "lightsensor":    
                    PushToStack(new CLightSensor());
                    break;

                case "touchsensor":
                    PushToStack(new CTouchSensor());
                    break;

                case "joint":
                    PushToStack(new CJoint());
                    break;

                case "gps":
                    PushToStack(new CGPS());
                    break;

                case "coordinate":
                    PushToStack(new CCoordinate());
                    break;

                case "texturecoordinate":
                    PushToStack(new CTextureCoordinate());
                    break;

                case "imagetexture":
                    PushToStack(new CImageTexture());
                    break;

                case "cylinder":
                    PushToStack(new CCylinder());
                    break;

                case "receiver":
                    PushToStack(new CReceiver());
                    break;

                case "emitter":
                    PushToStack(new CEmitter());
                    break;

                default:
                    PushToStack(new CUnknown(s));
                    break;
            }
        }

        public CWorldParserClass()
        {
            worldObjStack = new Stack<IWorldStackObject>();
            deferredParams = new Stack<string>();
            defDictionary = new Dictionary<string, IWorldStackObject>();
            Root = new CWorldArray();
            PushToStack(Root);
        }

        public CWorldParserClass(string fileName):this()
        {
            if (!File.Exists(fileName)) throw new FileNotFoundException();   
            readStream = File.OpenText(fileName);

            List<string> strWords;
            while ((strWords = Feed()) != null)
            {
                IWorldStackObject peek = worldObjStack.Peek();

                if (("}" == strWords[0]) && (worldObjStack.Count > 0))
                {
                    IWorldStackObject obj = StackPop();                 
                    
                    IWorldStackObject parent = worldObjStack.Peek();

                    if (parent is CWorldArray) (parent as CWorldArray).Add(obj);
                    else if (!(parent is CUnknown))
                    {
                        (parent as CWorldObject).PostParamByName(deferredParams.Pop(),obj);
                    }

                    continue;
                };

                if ("DEF" == strWords[0])
                {
                    dicKey = strWords[1];
                    strWords.RemoveRange(0, 2); // Implement defines
                }

                if ("USE" == strWords[0])
                {
                    continue;
                }

                if (("]" == strWords[0]) && (worldObjStack.Count > 0))
                {
                    CWorldArray o = (CWorldArray) StackPop();
                    
                    continue;
                };

                if (!(peek is CWorldArray) && !(peek is CUnknown))
                {
                    string param = strWords[0];                    
                    strWords.RemoveAt(0);

                    if ("USE" == strWords[0])
                        (peek as CWorldObject).PostParamByName(param, defDictionary[strWords[1]]);
                    // PushFromDictionary(strWords[1]); // Caution! 
                    else
                    {
                        if ("DEF" == strWords[0])
                        {
                            dicKey = strWords[1];
                            strWords.RemoveRange(0, 2); // Implement defines
                        }

                        if ("{" == strWords[strWords.Count - 1])
                            deferredParams.Push(param);
                        else
                            (worldObjStack.Peek() as CWorldObject).SetParamByName(worldObjStack, param, strWords);
                    }

                }

                if ("{" == strWords[strWords.Count - 1])
                {                 
                    IsWorldObject(strWords[strWords.Count - 2]);
                    continue;
                }

                if ((peek is CWorldArray))
                {
                    (peek as CWorldArray).Add(strWords);
                    continue;
                }
            }
            Console.WriteLine("\n DEF Dictonary size is {0}", defDictionary.Count);
            if (deferredParams.Count > 0) throw new IndexOutOfRangeException(String.Format("defferedParams list is not empty. There are {0} elements, parameter {1} is a top.",deferredParams.Count,deferredParams.Peek()) );
            if (worldObjStack.Count > 1) throw new IndexOutOfRangeException(String.Format("worldObjStack contain excess elements. There are {0} elements including root, parameter {1} is a top.", worldObjStack.Count, worldObjStack.Peek()));
        }
    }
}
